﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Threading;
using NativeWifi;
using System.Windows.Threading;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.ComponentModel;
using System.Collections.ObjectModel;

namespace NetwerkScanner
{

    #region Event Delegate signatures
    public delegate void HostDetectedEventHandler(HostDetectedEventArgs e);
    public delegate void BasicServiceSetDetectedEventHandler(BasicServiceSetDetectedEventArgs e);
    #endregion

    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {

        #region Events
        public event HostDetectedEventHandler HostDetected;
        public event BasicServiceSetDetectedEventHandler BasicServiceSetDetected;
        #endregion

        #region Properties
        public WlanClient Client { get; set; }
        public ObservableCollection<ExtendedServiceSet> ExtendedServiceSets { get; set; }
        #endregion

        #region Fields
        private Dictionary<string, ARPScanner> arpScanners = new Dictionary<string, ARPScanner>();
        private Dictionary<string, NetwerkSniffer> netwerkSniffers = new Dictionary<string, NetwerkSniffer>();
        #endregion

        #region Constructor

        public MainWindow()
        {

            InitializeComponent();
            App.Applicatie = this;
            Client = new WlanClient();
            ExtendedServiceSets = new ObservableCollection<ExtendedServiceSet>();
            _myTreeView.DataContext = ExtendedServiceSets;
            HostDetected += HostDetectedHandler;
            BasicServiceSetDetected += AccessPointDetectedHandler;

            new AccessPointScanner().Run();

            DispatcherTimer timer = new DispatcherTimer();
            timer.Interval = TimeSpan.FromSeconds(1);
            timer.Tick += DrawRadar;
            timer.Tick += UpdateGraph;
            timer.Start();
            Thread scanThread = new Thread(() =>
            {
                while (true)
                {
                    StartScans();
                    Thread.Sleep(5000);
                }
            });
            scanThread.IsBackground = true;
            scanThread.Start();
        }

        #endregion


        #region Scan Updater

        /// <summary>
        /// Start een arp scanner of sniffer voor netwerken waar er geen actief is
        /// Stopt arp scanners en sniffers die netwerken bestuderen waarmee de verbinding verbroken is
        /// </summary>
        public void StartScans()
        {
            try
            {
                List<string> drukBezigARP = new List<string>();
                List<string> drukBezigSniff = new List<string>();

                foreach (WlanClient.WlanInterface wlaninterface in Client.Interfaces)
                {
                    foreach (UnicastIPAddressInformation ipinfo in wlaninterface.NetworkInterface.GetIPProperties().UnicastAddresses)
                    {
                        if (ipinfo.Address.GetAddressBytes().Length == 4 && ipinfo.IPv4Mask != null)
                        {
                            string mask = ipinfo.IPv4Mask.ToString();
                            string binarymask = "";
                            mask.Split('.').ToList().ForEach(x => binarymask += Convert.ToString(Convert.ToInt32(x), 2));

                            string arpTarget = ipinfo.Address + "/" + binarymask.Replace("0", "").Length;
                            string sniffAddress = ipinfo.Address.ToString();
                            //arp
                            if (!arpScanners.ContainsKey(arpTarget))
                            {
                                ARPScanner arpScanner = new ARPScanner(arpTarget);
                                arpScanners[arpTarget] = arpScanner;
                                arpScanner.Run();
                                drukBezigARP.Add(arpTarget);
                            }
                            else if (arpScanners[arpTarget].klaar)
                            {
                                arpScanners.Remove(arpTarget);
                            }
                            else
                            {
                                drukBezigARP.Add(arpTarget);
                            }
                            //broadcast sniffer
                            if (!netwerkSniffers.ContainsKey(sniffAddress))
                            {
                                NetwerkSniffer sniffer = new NetwerkSniffer(ipinfo.Address);
                                netwerkSniffers[sniffAddress] = sniffer;
                                sniffer.Run();
                            }
                            drukBezigSniff.Add(sniffAddress);
                        }
                    }
                }
                //stop arp scans die bezig zijn maar niet meer verbonden
                foreach (KeyValuePair<string, ARPScanner> arp in arpScanners)
                {
                    if (!drukBezigARP.Contains(arp.Key))
                    {
                        arp.Value.Stop();
                        arpScanners.Remove(arp.Key);
                        break;
                    }
                }
                //stop broadcast sniffers die bezig zijn maar niet meer verbonden
                foreach (KeyValuePair<string, NetwerkSniffer> sniffer in netwerkSniffers)
                {
                    if (!drukBezigSniff.Contains(sniffer.Key))
                    {
                        sniffer.Value.Stop();
                        netwerkSniffers.Remove(sniffer.Key);
                        break;
                    }
                }
            }
            catch (Exception ex)
            {
                App.Logger.WriteLine("StartScans", ex.Message);
            }
        }

        #endregion

        #region Grafiek Updater

        /// <summary>
        /// Voegt een data punt toe aan de grafiek van elk netwerk met de huidige signaal sterkte
        /// </summary>
        /// <param name="source"></param>
        /// <param name="ea"></param>
        public void UpdateGraph(object source, EventArgs ea)
        {
            //Client.Interfaces[0].Scan(); //Tijdens een scan kunnen er geen data packets verstuurd worden wat alles vrij onstabiel maakt! als er geen verbinding is geen windows zelf constant scannen anders 
            DateTime now = DateTime.Now;
            foreach(ExtendedServiceSet ess in ExtendedServiceSets) {
                ess.Chart.AddDataPoint(new KeyValuePair<DateTime, int>(now, ess.RSSI));
            }
        }

        #endregion

        #region Radar Updater

        /// <summary>
        /// Tekent alle elementen op de rader opnieuw
        /// </summary>
        /// <param name="source"></param>
        /// <param name="ea"></param>
        public void DrawRadar(object source, EventArgs ea)
        {
            //Mogelijk databinden? http://www.iconstructions.be/blog/avalon-position-data-binding

            //Maakt de radar leeg
            radarCanvas.Children.Clear();
            int totaalActief = ExtendedServiceSets.Count(x => x.Active());
            double graden = 0;
            List<string> mijnSSID = getVerbondenSSIDs();
            //Tekent alle actieve netwerken
            foreach (ExtendedServiceSet ess in ExtendedServiceSets)
            {
                if (ess.Active())
                {
                    graden += 360.0 / totaalActief;
                    int straal = (int)(radarCanvas.ActualWidth / 2);
                    int rssi = ess.RSSI;
                    rssi = Math.Min(rssi, van.Value);
                    rssi = Math.Max(rssi, tot.Value);
                    double dist = (double)(van.Value - rssi) / (van.Value - tot.Value) * straal;
                    int x = (int)(radarCanvas.ActualWidth / 2 + dist * Math.Cos((Math.PI / 180) * graden));
                    int y = (int)(radarCanvas.ActualHeight / 2 + dist * Math.Sin((Math.PI / 180) * graden));
                    
                    SolidColorBrush color = new SolidColorBrush(Color.FromArgb((byte)(255 - DateTime.Now.Subtract(ess.LastUpdate).Seconds * 25), 0, 0, 0));
                    if (mijnSSID.Contains(ess.SSID))
                    {
                        //Verbonden met deze SSID, andere kleur
                        color = new SolidColorBrush(Color.FromArgb((byte)(255 - DateTime.Now.Subtract(ess.LastUpdate).Seconds * 25), 255, 215, 0));
                    }
                    //SSID Label
                    TextBlock textBlock = new TextBlock();
                    textBlock.Text = ess.SSID;
                    textBlock.Foreground = (color);
                    Canvas.SetLeft(textBlock, x - 32);
                    Canvas.SetTop(textBlock, y - 16);
                    radarCanvas.Children.Add(textBlock);
                    //SSID Punt
                    Ellipse el = new Ellipse();
                    el.Width = el.Height = 5;
                    el.Fill = color;
                    Canvas.SetLeft(el, x - 2);
                    Canvas.SetTop(el, y - 2);
                    radarCanvas.Children.Add(el);
                }
            }
        }

        /// <summary>
        /// Reset de schaal van de rader
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ResetClick(object sender, RoutedEventArgs e)
        {
            van.Value = -40;
            tot.Value = -70;
        }

        #endregion


        #region Access Point Event Handler

        public void AccessPointDetectedHandler(BasicServiceSetDetectedEventArgs e)
        {
            try
            {
                //Netwerk zoeken met dezelfde SSID als de gevonden access point
                ExtendedServiceSet ess = ExtendedServiceSets.FirstOrDefault(n => n.SSID == e.DetectedBasicServiceSet.SSID);
                if (ess == null)
                {
                    //Access point heeft een netwerk dat we nog niet kennen
                    ess = new ExtendedServiceSet(e.DetectedBasicServiceSet.SSID);
                    ess.Chart = new ConnectivityChart();
                    ess.Chart.Header = e.DetectedBasicServiceSet.SSID;
                    tabControl1.Items.Add(ess.Chart);
                    ExtendedServiceSets.Add(ess);
                }
                //Kijken of deze access point al bekend was in het netwerk
                BasicServiceSet bss = ess.BasicServiceSets.FirstOrDefault(n => n.MACAddress == e.DetectedBasicServiceSet.MACAddress);
                if (bss != null)
                {
                    //Signaal sterkte en tijd updaten
                    bss.Update(e.DetectedBasicServiceSet);
                }
                else
                {
                    //Access point toevoegen aan het netwerk
                    ess.BasicServiceSets.Add(e.DetectedBasicServiceSet);
                    //Webservice
                    App.NetwerkService.AddAccesPointsAsync(ess.SSID, e.DetectedBasicServiceSet.MACAddress);

                }
            }
            catch (Exception ex)
            {
                App.Logger.WriteLine("AccessPointDetected", ex.Message);
            }
        }

        #endregion

        #region Host Event Handler

        public void HostDetectedHandler(HostDetectedEventArgs e)
        {
            try
            {
                List<string> mijnSSIDs = getVerbondenSSIDs();
                IPAddress address = IPAddress.Parse(e.DetectedHost.IPAdres);
                //Zoekt in de verbonden netwerken of de gevonden host in het subnet ligt
                var ess = ExtendedServiceSets.Where(x => mijnSSIDs.Contains(x.SSID)).FirstOrDefault(x =>
                {
                    foreach (UnicastIPAddressInformation ip in getIPBySSID(x.SSID).UnicastAddresses)
                    {
                        if (ip.IPv4Mask != null && ip.Address.AddressFamily == AddressFamily.InterNetwork && CheckMask(ip.Address, ip.IPv4Mask, address))
                        {
                            return true;
                        }
                    }
                    return false;
                });
                if (ess == null)
                {
                    //Console.WriteLine("Extern ip: " + e.DetectedHost.IPAdres);
                    return;
                }
                
                Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>
                {
                    Host h = ess.Hosts.FirstOrDefault(x => x.IPAdres == e.DetectedHost.IPAdres);
                    if (h == null)
                    {
                        ess.Hosts.Add(e.DetectedHost);
                        //Webservice
                        App.NetwerkService.AddHostsAsync(ess.SSID, e.DetectedHost.IPAdres, e.DetectedHost.MACAddress, DateTime.Now.ToString());
                      
                    }
                    else
                    {
                        //Als de host gevonden werd via broadcast sniffing dan hebben we het MAC adres nog niet
                        if (e.DetectedHost.MACAddress != "Onbekend")
                        {
                            h.MACAddress = e.DetectedHost.MACAddress;
                        }
                        if (e.DetectedHost.Ping != -1)
                        {
                            h.Ping = e.DetectedHost.Ping;
                        }
                    }
                }));
            }
            catch(Exception ex)
            {
                App.Logger.WriteLine("HostDetected", ex.Message);
            }
        }

        #endregion

        #region Tree view double click handler
        private void OnItemMouseDoubleClick(object sender, RoutedEventArgs e)
        {
            if ((sender as TreeViewItem).Header is ExtendedServiceSet)
            {
                ExtendedServiceSet ess = (sender as TreeViewItem).Header as ExtendedServiceSet;
                if (!tabControl1.Items.Contains(ess.Chart))
                {
                    tabControl1.Items.Add(ess.Chart);
                }
            }
        }
        #endregion

        #region SSID Methodes
        /// <summary>
        /// Geeft een lijst met alle SSIDS van de netwerken waarmee we verbonden zijn terug.
        /// </summary>
        /// <returns></returns>
        private List<string> getVerbondenSSIDs()
        {
            try
            {
                return Client.Interfaces
                    .Where(x => x.InterfaceState != Wlan.WlanInterfaceState.Disconnecting)
                    .Select(x => toSSID(x.CurrentConnection.wlanAssociationAttributes.dot11Ssid)) //exc
                .ToList();
            }
            catch
            {
                return new List<string>();
            }
        }

        /// <summary>
        /// Geeft de ip properties van de adapter die met een bepaalde SSID verbonden is terug.
        /// </summary>
        /// <param name="ssid"></param>
        /// <returns></returns>
        private IPInterfaceProperties getIPBySSID(string ssid)
        {
            try
            {
                return Client.Interfaces
                    .Where(x =>
                        x.InterfaceState != Wlan.WlanInterfaceState.Disconnecting
                        &&
                        toSSID(x.CurrentConnection.wlanAssociationAttributes.dot11Ssid) == ssid)
                    .Select(x => x.NetworkInterface.GetIPProperties())
                .FirstOrDefault();
            }
            catch
            {
                return null;
            }
        }

        /// <summary>
        /// Zet een dot11Ssid om in leesbare vorm.
        /// </summary>
        /// <param name="dot11Ssid"></param>
        /// <returns></returns>
        private static string toSSID(Wlan.Dot11Ssid dot11Ssid)
        {
            return Encoding.ASCII.GetString(dot11Ssid.SSID, 0, (int)dot11Ssid.SSIDLength);
        }

        #endregion

        #region IP Methodes

        /// <summary>
        /// Geeft true terug als het opgegeven target address in het opgegeven subnet ligt
        /// </summary>
        /// <param name="address"></param>
        /// <param name="mask"></param>
        /// <param name="target"></param>
        /// <returns></returns>
        private static bool CheckMask(IPAddress address, IPAddress mask, IPAddress target)
        {
            var bytes1 = address.GetAddressBytes();
            var bytesMask = mask.GetAddressBytes();
            var bytes2 = target.GetAddressBytes();

            if (bytes1.Length != bytesMask.Length || bytesMask.Length != bytes2.Length)
                return false;

            for (var i = 0; i < bytesMask.Length; i++)
            {
                if ((bytes1[i] & bytesMask[i]) != (bytes2[i] & bytesMask[i]))
                    return false;
            }

            return true;
        }

        #endregion

        #region Publieke event invocation
        public void BasicServiceSetDetectedEvent(BasicServiceSetDetectedEventArgs e)
        {
            BasicServiceSetDetected(e);
        }

        public void HostDetectedEvent(HostDetectedEventArgs e)
        {
            HostDetected(e);
        }
        #endregion

    }

}
